module fifo #(
    parameter int unsigned WIDTH = 8,
    parameter int unsigned DEPTH = 16
) (
    input  logic             clk,
    input  logic             rst_n,
    input  logic [WIDTH-1:0] in_data,
    input  logic             in_valid,
    output logic             in_ready,
    output logic [WIDTH-1:0] out_data,
    output logic             out_valid,
    input  logic             out_ready
);

    localparam int unsigned ADDR_WIDTH = $clog2(DEPTH);
    logic [          WIDTH-1:0] mem    [DEPTH];
    logic [     ADDR_WIDTH-1:0] wr_ptr;
    logic [     ADDR_WIDTH-1:0] rd_ptr;
    logic [$clog2(DEPTH+1)-1:0] count;
    logic                       full;
    logic                       empty;

    assign full      = (count == DEPTH);
    assign empty     = (count == 0);
    assign in_ready  = !full;
    assign out_valid = !empty;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_ptr <= '0;
        end else begin
            if (in_valid && in_ready) begin
                mem[wr_ptr] <= in_data;
                if (wr_ptr == DEPTH - 1) begin
                    wr_ptr <= '0;
                end else begin
                    wr_ptr <= wr_ptr + 1;
                end
            end
        end
    end

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_ptr   <= '0;
            out_data <= '0;
        end else begin
            if (out_valid && out_ready) begin
                if (rd_ptr == DEPTH - 1) begin
                    rd_ptr <= '0;
                end else begin
                    rd_ptr <= rd_ptr + 1;
                end
            end
            out_data <= mem[rd_ptr];
        end
    end

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            count <= '0;
        end else begin
            case ({
                in_valid && in_ready, out_valid && out_ready
            })
                2'b10:   count <= count + 1;
                2'b01:   count <= count - 1;
                default: count <= count;
            endcase
        end
    end

endmodule

